// Copyright 2019 clair authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package vulnerability

import (
	"database/sql"

	"github.com/quay/clair/v3/database"
	"github.com/quay/clair/v3/database/pgsql/feature"
	"github.com/quay/clair/v3/database/pgsql/util"
	"github.com/quay/clair/v3/ext/versionfmt"
	"github.com/lib/pq"
)

const (
	searchPotentialAffectingVulneraibilities = `
	SELECT nf.id, v.id, vaf.affected_version, vaf.id
	FROM vulnerability_affected_feature AS vaf, vulnerability AS v,
		namespaced_feature AS nf, feature AS f
	WHERE nf.id = ANY($1)
		AND nf.feature_id = f.id
		AND nf.namespace_id = v.namespace_id
		AND vaf.feature_name = f.name
		AND vaf.feature_type = f.type
		AND vaf.vulnerability_id = v.id
		AND v.deleted_at IS NULL`
	insertVulnerabilityAffected = `
		INSERT INTO vulnerability_affected_feature(vulnerability_id, feature_name, affected_version, feature_type, fixedin)
		VALUES ($1, $2, $3, $4, $5)
		RETURNING ID
	`
	searchVulnerabilityAffected = `
	SELECT vulnerability_id, feature_name, affected_version, t.name, fixedin 
	FROM vulnerability_affected_feature AS vaf, feature_type AS t
	WHERE t.id = vaf.feature_type AND vulnerability_id = ANY($1)
	`

	searchVulnerabilityPotentialAffected = `
	WITH req AS (
		SELECT vaf.id AS vaf_id, n.id AS n_id, vaf.feature_name AS name, vaf.feature_type AS type, v.id AS vulnerability_id
		FROM vulnerability_affected_feature AS vaf,
			vulnerability AS v,
			namespace AS n
		WHERE vaf.vulnerability_id = ANY($1)
		AND v.id = vaf.vulnerability_id
		AND n.id = v.namespace_id
		)
	SELECT req.vulnerability_id, nf.id, f.version, req.vaf_id AS added_by
	FROM feature AS f, namespaced_feature AS nf, req
	WHERE f.name = req.name
	AND f.type = req.type
	AND nf.namespace_id = req.n_id
	AND nf.feature_id = f.id`
)

type vulnerabilityCache struct {
	nsFeatureID     int64
	vulnID          int64
	vulnAffectingID int64
}

func SearchAffectingVulnerabilities(tx *sql.Tx, features []database.NamespacedFeature) ([]vulnerabilityCache, error) {
	if len(features) == 0 {
		return nil, nil
	}

	ids, err := feature.FindNamespacedFeatureIDs(tx, features)
	if err != nil {
		return nil, err
	}

	fMap := map[int64]database.NamespacedFeature{}
	for i, f := range features {
		if !ids[i].Valid {
			return nil, database.ErrMissingEntities
		}
		fMap[ids[i].Int64] = f
	}

	cacheTable := []vulnerabilityCache{}
	rows, err := tx.Query(searchPotentialAffectingVulneraibilities, pq.Array(ids))
	if err != nil {
		return nil, util.HandleError("searchPotentialAffectingVulneraibilities", err)
	}

	defer rows.Close()
	for rows.Next() {
		var (
			cache    vulnerabilityCache
			affected string
		)

		err := rows.Scan(&cache.nsFeatureID, &cache.vulnID, &affected, &cache.vulnAffectingID)
		if err != nil {
			return nil, err
		}

		if ok, err := versionfmt.InRange(fMap[cache.nsFeatureID].VersionFormat, fMap[cache.nsFeatureID].Version, affected); err != nil {
			return nil, err
		} else if ok {
			cacheTable = append(cacheTable, cache)
		}
	}

	return cacheTable, nil
}
